---
title: Architecture
summary: Hosting model, request pipeline, layering, and the dependency injection composition root for PdfAPI.
status: draft
owner: Not determined from source
last_reviewed: 2026-09-17
library: PdfAPI
reference_type: embedded
---

# Architecture

PdfAPI is hosted in IIS as a single ASP.NET Web API 2 application with no OWIN self-hosting and no WCF surface. Startup wires Web API configuration and an Autofac container before any request is served; every controller resolves a per-request PDF service and, where tracking applies, a per-request NHibernate unit of work against the Oracle `IMG` schema.

## Hosting model

The service is hosted in IIS. `Global.asax` instantiates `WebApiApplication` :
`System.Web.HttpApplication`, whose `Application_Start` runs the `App_Start` configuration classes, including `WebApiConfig.Register(HttpConfiguration)`.

| Aspect | Finding |
| --- | --- |
| Host | IIS (`System.Web.HttpApplication`) — no OWIN, no WCF, no self-host |
| Application class | `WebApiApplication` (`PdfAPI\Global.asax.cs`) |
| Startup call chain | `Application_Start` → `LicenseHelper.LicenseSyncfusion()` → `AreaRegistration.RegisterAllAreas()` → `GlobalConfiguration.Configure(WebApiConfig.Register)` → `FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)` → `RouteConfig.RegisterRoutes(RouteTable.Routes)` → `BundleConfig.RegisterBundles(BundleTable.Bundles)` → build and configure the Autofac container (`ContainerBuilder`, `RegisterControllers`, `RegisterApiControllers`, `IoCConfig.RegisterDependencies`, `builder.Build()`) → assign `DependencyResolver` and `GlobalConfiguration.Configuration.DependencyResolver` |
| Virtual directory / app pool | Not determined from source** |
| Target framework | .NET Framework 4.6.2 |

## Request pipeline

The following diagram shows how a request reaches a controller action and how the NHibernate unit of work is scoped for tracked operations.

```mermaid
flowchart TD
    CLIENT["Client request"] --> IIS["IIS + HttpApplication"]
    IIS --> HANDLER["ApiResponseHandler<br>DelegatingHandler"]
    HANDLER --> ROUTE["Attribute routing<br>MapHttpAttributeRoutes"]
    ROUTE --> CTRL["Controller : ApiController"]
    CTRL --> FACTORY["PdfServiceFactory.Create()<br>reads pdfapi.pdfservicetype"]
    FACTORY --> SVC["SyncfusionPdfService<br>or ActivePdfService"]
    SVC --> TRACK["PdfTrackingService"]
    TRACK --> UOW["NHibernate IUnitOfWork<br>IUnitOfWorkFactory(&quot;oracle&quot;)"]
    UOW --> DB["Oracle IMG schema"]
    SVC --> CORE["PdfAPI.Core / PdfApi.SyncFusionCore<br>format operations"]
```

## Layer responsibilities

| Layer | Type | Responsibility |
| --- | --- | --- |
| `WebApiApplication` | `System.Web.HttpApplication` | Application start-up and configuration wiring |
| `PdfAPI\App_Start\WebApiConfig.cs` | Configuration | Message handler, formatter, route, and error-detail-policy configuration |
| `ConvertController`, `ExtractController`, `MergeController`, `LegacyController`, `InfoController`, `ApiInfoController` | `System.Web.Http.ApiController` | Request binding and delegation to the service layer; four of six controllers add per-action `try`/`catch` error handling |
| `PdfServiceFactory` / `PdfService` (abstract) / `SyncfusionPdfService` / `ActivePdfService` | Service layer | Selects and executes the PDF processor, coordinates tracking and incident email |
| `PdfTrackingService` | NHibernate | Persistence through `IUnitOfWork` against the Oracle `IMG` schema — the sole data-access component in the repository |

## Routing configuration

| Aspect | Finding |
| --- | --- |
| `MapHttpAttributeRoutes()` called | Yes |
| Convention route templates | `api/{controller}/{id}` (`DefaultApi`, `id` optional) — registered but matched by no live action, because every action carries `[Route]`* |
| Route prefix / versioning | `[RoutePrefix("v1")]` (`ConvertController`, `ExtractController`, `MergeController`), `[RoutePrefix("v1/legacy")]` (`LegacyController`), `[RoutePrefix("v1/info")]` (`InfoController`); `ApiInfoController` has no `[RoutePrefix]` |
| Controller folder layout | All six controllers live in `PdfAPI\Controllers\v1\`; the folder name contributes nothing to the URL — only `[RoutePrefix]`/`[Route]` values do |

## Base controller

Every controller declares `System.Web.Http.ApiController` directly. There is no repository-local base controller (no `ApiBase`-style type), so no shared response construction, logging, or identity resolution is contributed by inheritance.

| Controller | Base | Inherited behavior |
| --- | --- | --- |
| `ConvertController`, `ExtractController`, `MergeController`, `LegacyController`, `InfoController`, `ApiInfoController` | `ApiController` | — |

## Composition root

**Composition root location, as actually found in this repository:** `PdfAPI\App_Start\IocConfig.cs` — the file name carries a **lowercase `c`** (`IocConfig.cs`), while the class it declares is `IoCConfig` with `public static void RegisterDependencies(ContainerBuilder builder)`. This path was discovered behaviorally, not by filename convention: the Autofac API surface (`new Autofac.ContainerBuilder()`, `builder.Build()`, `AutofacDependencyResolver`, `AutofacWebApiDependencyResolver`, `RegisterCommonDependencies(`) was searched across the whole solution closure and traced back from `PdfAPI\Global.asax.cs` → `Application_Start`. No `Autofac.Module` subclass, no `RegisterAssemblyTypes`, and no separate `*.Dependencies` or `SAIF.DependencyMaps.*` project exists in this repository.

| Field | Value |
| --- | --- |
| **Project** | `PdfAPI` |
| **File** | `PdfAPI\App_Start\IocConfig.cs` |
| **Entry method** | `IoCConfig.RegisterDependencies(ContainerBuilder)` |
| **Invoked from** | `PdfAPI\Global.asax.cs` → `Application_Start` (after `RegisterControllers`/`RegisterApiControllers`, before `builder.Build()`) |
| **Resolver** | Both `AutofacDependencyResolver` (MVC) and `AutofacWebApiDependencyResolver` (Web API) are assigned in `Global.asax.cs` |
| **Autofac version** | Not read from `packages.config` in this analysis pass — see Dependencies for package versions |
| **Additional registration files** | None |

The container is built across `PdfAPI\Global.asax.cs` and `PdfAPI\App_Start\IocConfig.cs` together: `Global.asax.cs` creates the `ContainerBuilder`, registers MVC and Web API controllers by assembly scan, calls `IoCConfig.RegisterDependencies(builder)`, builds the container, and assigns both dependency resolvers. `IocConfig.cs` calls `builder.RegisterCommonDependencies(typeof(HttpApplication))` (from `SAIF.Web.Mvc.Extensions`) to supply `ILogService`, `IEnvironmentService`, and the named `IUnitOfWorkFactory("oracle")`, then layers on the two application-specific registrations below.

| Registration | Lifetime | Resolved by | Purpose |
| --- | --- | --- | --- |
| `builder.RegisterCommonDependencies(typeof(HttpApplication))` | Determined inside the extension method — not visible in this repository** | All controllers and services | Supplies `ILogService`, `IEnvironmentService`, and `IUnitOfWorkFactory("oracle")` |
| `IUnitOfWork` (lambda over `IUnitOfWorkFactory("oracle").GetUnitOfWork(DbConnectionManager.GetOracleConnectionString(IEnvironmentService, EnvironmentConfigKeys.OracleDatabase, "IMG"))`) | `InstancePerRequest` | `PdfServiceFactory`, `PdfService`, `PdfTrackingService` | NHibernate unit of work for the Oracle `IMG` schema |
| `PdfServiceFactory` (concrete type, no interface) | `InstancePerRequest` | `ConvertController`, `ExtractController`, `MergeController`, `LegacyController`, `InfoController` | Selects and constructs the PDF processor per request |

`IPdfService` is **not** registered with Autofac; controllers receive `PdfServiceFactory` and call `.Create()` themselves, so the concrete processor is selected per request at runtime rather than by container configuration ([BR-PDFAPI-003](business-rules.md#br-pdfapi-003-processor-selection-by-configuration)).

## Error handling

Three mechanisms co-exist in this repository; they are **not** interchangeable, and one does not apply to API endpoints at all.

| Mechanism | Registered in | Scope | Behavior |
| --- | --- | --- | --- |
| `ApiResponseHandler` (`DelegatingHandler`, package `SAIF.Web.API`) | `config.MessageHandlers.Add(new ApiResponseHandler());` in `PdfAPI\App_Start\WebApiConfig.cs` | Global — every Web API request/response, including framework-produced failures | Wraps/post-processes the HTTP response for the whole service; the exact envelope shape is implemented inside the `SAIF.Web.API` package and is not visible in this repository** |
| Per-action `try`/`catch` + `ILogService.LogException` | `ConvertController` (both actions), `ExtractController`, `MergeController`, `LegacyController` | Per-action (5 of 13 endpoints) | The action swallows the exception, logs it, and returns `Ok(new PdfResult("<message>", request))` — HTTP `200` with `Status = Failed` |
| MVC `HandleErrorAttribute` | `filters.Add(new HandleErrorAttribute());` in `PdfAPI\App_Start\FilterConfig.cs` | MVC controllers only (`GlobalFilters.Filters`) — has **no effect** on any `ApiController` action | Not part of the API error contract |

`config.Filters` is never populated — there is no Web API `ExceptionFilterAttribute`, no `IExceptionHandler`, and no `IExceptionLogger`. `config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;` applies in every environment, so an unhandled exception produces full framework exception detail (message, type, stack trace) before the response reaches `ApiResponseHandler`. That handler's final wrapping/post-processing and the actual client-visible envelope are not visible in this repository** (see [Endpoints: Error response shape](endpoints.md#error-response-shape)). `InfoController` (7 endpoints) and `ApiInfoController` (1 endpoint) have no `try`/`catch`, so their validation failures surface as framework errors rather than as a `PdfResult`.

## API documentation surface

| Aspect | Finding |
| --- | --- |
| Swagger / Swashbuckle | Active — via `[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]` in `PdfAPI\App_Start\SwaggerConfig.cs`; there is no explicit `SwaggerConfig.Register()` call in `Application_Start` |
| Exposed route | Not determined from source** — `SwaggerConfig.Register()` calls `EnableSwagger(c => c.SingleApiVersion("v1", "PdfAPI")).EnableSwaggerUi()`, with no custom `RootUrl` configured |
| Package and version | `Swashbuckle` / `Swashbuckle.Core` `5.3.2`, `WebActivatorEx` `2.0` |

## Telemetry

| Aspect | Finding |
| --- | --- |
| Instrumentation | None found — a search for `ConfigureOpenTelemetry(` across the solution closure returned zero matches |
| Registered in | — |

The only observability in force is logging through `ILogService` (`LogException`, `TraceError`, `TraceWarn`, `TraceDebug`) plus `log4net` `2.0.5`, and a stray `Console.WriteLine(result.ToString())` at the end of `SyncfusionPdfService.FinalizeResult` (see [Tech debt](tech-debt.md)).

## Design constraints
- The NHibernate unit of work is scoped `InstancePerRequest`, so the transaction boundary for tracking writes is the HTTP request, not the business operation.
- `PdfTrackingService` is the sole data-access component; every controller and service reaches the database only through it.
- `IncludeErrorDetailPolicy.Always` produces framework exception detail before `ApiResponseHandler` runs, in every environment; the final client-visible envelope is unknown** (see [Endpoints: Error response shape](endpoints.md#error-response-shape)) — this is a design constraint with security implications regardless (see [Tech debt](tech-debt.md)).

## Integration points

| System | Type | Purpose |
| --- | --- | --- |
| Oracle `IMG` schema | Database | Tracking record persistence for convert/merge requests |
| ActivePDF server (UNC working directory) | Downstream service | Alternate PDF processor, selected by `pdfapi.pdfservicetype == "activepdf"` |
| SMTP (`mail.saif.com`) | Downstream service | Failure incident email sent from `PdfService.SendIncidentEmail` |

## Related documentation
- [API index](index.md)
- [Overview](overview.md)
- [Endpoints](endpoints.md)
- [Authentication and authorization](authentication-authorization.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with `**` could not be confirmed; treat as unknown until verified.

## Sources
- PdfAPI\Global.asax.cs
- PdfAPI\App_Start\WebApiConfig.cs
- PdfAPI\App_Start\IocConfig.cs
- PdfAPI\App_Start\SwaggerConfig.cs
- PdfAPI\App_Start\FilterConfig.cs
- PdfAPI\Services\PdfServiceFactory.cs
- PdfAPI\Services\PdfService.cs
- PdfAPI\Services\PdfTrackingService.cs
- PdfAPI\Services\Syncfusion\SyncfusionPdfService.cs
- PdfAPI\Services\ActivePdf\ActivePdfService.cs
